package com.example.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.ReadFrom;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.logging.log4j.util.Strings;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.StringUtils;

import java.util.*;

@Configuration
@EnableConfigurationProperties({RedisExtendProperties.class})
public class RedisConfig {

    @Autowired
    RedisProperties redisProperties;

    @Autowired
    RedisExtendProperties redisExtendProperties;

    //===============================Redisson分布式客户端===========================
    //需要我们自己配置Redisson
//    @Bean
//    public RedissonClient redissonClient(){
//        Config config=new Config();
//        //,,,,,
//
//        return Redisson.create(config);
//    }
    //================================lettuce客户端================================
    /**
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean(name = "redisTemplate")
    RedisTemplate<String,String> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws InstantiationException, IllegalAccessException {
        return createRedisTemplate(String.class,redisConnectionFactory);
    }
    @Bean(name = "redisTemplate2")
    RedisTemplate<String,Object> redisTemplate2(RedisConnectionFactory redisConnectionFactory) throws InstantiationException, IllegalAccessException {
        return createRedisTemplate(Object.class,redisConnectionFactory);
    }

    /**
     *
     * @return
     */
    @Bean(name = "redisTemplate3")
    RedisTemplate<String,String> redisTemplate3() throws InstantiationException, IllegalAccessException {
        // 配置单个redis的配置类
//        RedisStandaloneConfiguration redisStandaloneConfiguration = redisStandaloneConfiguration(redisProperties);

        //配置集群redis的配置类
//        RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
//        clusterConfiguration.setPassword(redisProperties.getPassword());
//        Set<RedisNode> nodes = new HashSet<RedisNode>();
//        List<String> clusterNodes = redisProperties.getCluster().getNodes();
//        //按127.0.0.1:6380，按：分割，并去除分割后字符串前后的空格
//        clusterNodes.forEach(address -> nodes.add(new RedisNode(address.split(":")[0].trim(), Integer.valueOf(address.split(":")[1]))));
//        clusterConfiguration.setClusterNodes(nodes);
//        if (redisProperties.getCluster().getMaxRedirects()!=null)
//            clusterConfiguration.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());
        //哨兵模式，哨兵配置。
//        RedisSentinelConfiguration sentinelConfiguration=new RedisSentinelConfiguration();
//        sentinelConfiguration.master(redisProperties.getSentinel().getMaster());
//        sentinelConfiguration.setPassword(redisProperties.getSentinel().getPassword());
//        Set<RedisNode> nodes = new HashSet<RedisNode>();
//        List<String> sentinelNodes = redisProperties.getCluster().getNodes();
//        //按127.0.0.1:6380，按：分割，并去除分割后字符串前后的空格
//        sentinelNodes.forEach(address -> nodes.add(new RedisNode(address.split(":")[0].trim(), Integer.valueOf(address.split(":")[1]))));
//        sentinelConfiguration.setSentinels(nodes);

        //主从复制集群配置。这个可以实现故障迁移。
        RedisStaticMasterReplicaConfiguration replicaConfiguration = new RedisStaticMasterReplicaConfiguration(redisProperties.getHost(),redisProperties.getPort());
        //添加从服务器。
        List<String> slaves = redisExtendProperties.getSlaves();
        slaves.forEach(node -> replicaConfiguration.addNode(node.split(":")[0].trim(),Integer.valueOf(node.split(":")[1])));
//        List<RedisNode> slaves = redisExtendProperties.getSlaves();
//        for(RedisNode node:slaves)
//            replicaConfiguration.addNode(node.getHost(),node.getPort());
        replicaConfiguration.setPassword(redisProperties.getPassword());


        // 根据连接池配置信息，创建连接池
        //GenericObjectPoolConfig<String> stringGenericObjectPoolConfig = genericTPoolConfig(String.class,redisProperties2.redisProperties.getLettuce().getPool());
        GenericObjectPoolConfig<String> stringGenericObjectPoolConfig = genericTPoolConfig(String.class,redisProperties.getLettuce().getPool());

        // 根据配置信息创建连接工厂
        LettuceConnectionFactory lettuceConnectionFactory = createLettuceConnectionFactory(redisProperties,replicaConfiguration, stringGenericObjectPoolConfig);
        return createRedisTemplate(String.class,lettuceConnectionFactory);
    }

    /**
     * 读取配置文件的信息，生成对应的单个redis配置对象
     * @param redisProperties redis配置信息类
     * @return
     */
    RedisStandaloneConfiguration redisStandaloneConfiguration(RedisProperties redisProperties){
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(redisProperties.getHost());
        configuration.setPort(redisProperties.getPort());
        configuration.setDatabase(redisProperties.getDatabase());
        if (Strings.isNotBlank(redisProperties.getPassword())) {
            configuration.setPassword(RedisPassword.of(redisProperties.getPassword()));
        }
        return configuration;
    }

    /**
     * 配置对象池
     * @param tClass
     * @param poolProperties 连接池的参数信息
     * @param <T> 对象池接收的类型
     * @return
     */
    <T> GenericObjectPoolConfig<T> genericTPoolConfig(Class<T> tClass,RedisProperties.Pool poolProperties){
        // 连接池配置
        GenericObjectPoolConfig<T> genericObjectPoolConfig = new GenericObjectPoolConfig<>();
        genericObjectPoolConfig.setMaxTotal(poolProperties.getMaxActive());
        genericObjectPoolConfig.setMaxIdle(poolProperties.getMaxIdle());
        genericObjectPoolConfig.setMinIdle(poolProperties.getMinIdle());
        if (poolProperties.getTimeBetweenEvictionRuns() != null) {
            genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(poolProperties.getTimeBetweenEvictionRuns().toMillis());
        }
        if (poolProperties.getMaxWait() != null) {
            genericObjectPoolConfig.setMaxWaitMillis(poolProperties.getMaxWait().toMillis());
        }
        return genericObjectPoolConfig;
    }

    /**
     * 根据redisConfiguration配置信息类、对象池创建LettuceConnectionFactory
     * 创建一个客户端连接配置的建筑类。用于创建客户端连接配置类。而客户端连接配置类是用于创建客户端连接工厂。
     * 而客户端连接工厂是用于创建客户端连接
     * @param redisProperties 配置文件的参数类
     * @param redisConfiguration redis配置信息类
     * @param genericTPoolConfig 对象池
     * @return 返回对应的连接工厂
     */
    LettuceConnectionFactory createLettuceConnectionFactory(RedisProperties redisProperties,RedisConfiguration redisConfiguration,GenericObjectPoolConfig genericTPoolConfig){
//==================================客户端选项配置======================================
        //开启 自适应集群拓扑刷新和周期拓扑刷新
        ClusterTopologyRefreshOptions.Builder clusterOptionsBuilder=ClusterTopologyRefreshOptions.builder();
        RedisProperties.Lettuce.Cluster.Refresh refresh = redisProperties.getLettuce().getCluster().getRefresh();
        //#集群拓扑刷新周期。
        if (refresh.getPeriod() != null) {
            clusterOptionsBuilder.enablePeriodicRefresh(refresh.getPeriod());
        }
        //#是否应该使用使用所有可用刷新触发器的自适应拓扑刷新。自适应刷新不开启,Redis集群变更时将会导致连接异常
        if (refresh.isAdaptive()) {
            clusterOptionsBuilder.enableAllAdaptiveRefreshTriggers();
        }
        //创建客户端选项
        ClientOptions clientOptions = ClusterClientOptions.builder()
                //配置拓扑刷新选项。
                .topologyRefreshOptions(clusterOptionsBuilder.build())
                .build();
//=====================================================================客户端连接配置的建筑类、客户端连接配置
        //创建一个客户端连接配置的建筑类。用于创建客户端连接配置类。而客户端连接配置类是用于创建客户端连接工厂
        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
        //客户端连接工厂的建筑类的一些配置
        if (redisProperties.isSsl()) {
            builder.useSsl();
        }
        if (redisProperties.getTimeout() != null) {
            builder.commandTimeout(redisProperties.getTimeout());
        }
        if (redisProperties.getLettuce() != null) {
            RedisProperties.Lettuce lettuce = redisProperties.getLettuce();
            if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
                builder.shutdownTimeout(redisProperties.getLettuce().getShutdownTimeout());
            }
        }
        if (StringUtils.hasText(redisProperties.getClientName())) {
            builder.clientName(redisProperties.getClientName());
        }
        //为客户端连接工厂的建筑类配置上对象池、？、配置客户端选项
        builder.poolConfig(genericTPoolConfig).readFrom(ReadFrom.REPLICA_PREFERRED).clientOptions(clientOptions);
        //通过客户端连接工厂的建筑类创建一个客户端连接配置对象
        LettuceClientConfiguration clientConfig = builder.build();

//===================================================================redis客户端连接工厂、客户端连接工厂的配置
        /*通过配置文件、连接池创建一个redis客户端连接工厂*/                                                //clusterConfiguration
        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisConfiguration,clientConfig);
        /*设置参数后的后处理，这是必须的*/
        lettuceConnectionFactory.afterPropertiesSet();
        //是否允许多个线程操作共用同一个缓存连接，默认true，false时每个操作都将开辟新的连接
        lettuceConnectionFactory.setShareNativeConnection(false);
        // 重置底层共享连接, 在接下来的访问时初始化
        lettuceConnectionFactory.resetConnection();
        return  lettuceConnectionFactory;
    }
    /**
     * 根据redisConnectionFactory连接到的redis服务器，创建一个redisTemplate对象
     * @param redisConnectionFactory
     * @return 返回创建的redisTemplate对象
     */
    <T> RedisTemplate<String,T> createRedisTemplate(Class<T> tClass,RedisConnectionFactory redisConnectionFactory) throws InstantiationException, IllegalAccessException {
        //泛型不一定RedisTemplate要在传入参数的时候指定，也可以返回的时候，根据赋值类型指定
        RedisTemplate<String, T> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(redisConnectionFactory);

        //  redisTemplate.setEnableTransactionSupport(false);
//====================================自定义配置==================================================//
        //================================redis序列化配置================//
        //默认是jdk序列化，可以去RedisSerializer查看所有的序列化类
        //redisTemplate.setDefaultSerializer(new JdkSerializationRedisSerializer(new SerializingConverter(), new DeserializingConverter()));
        //配置需要json处理的类，object：处理所有类
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        /**
         * //定义一个模型,该模型使用`CarDeserializer`作为反序列化器
         * SimpleModule module = new SimpleModule("CarDeserializer", new Version(3, 1, 8, null, null, null));
         * //指定反序列化器作用的Java类(Car类)
         * module.addDeserializer(Car.class, new CarDeserializer(Car.class));
         * //为ObjectMapper添加一个序列化/反序列化的模型
         * mapper.registerModule(module);
         */
        /**
         * 有时候你要读取的JSON数据的属性要多于你的Java对象的属性, 默认情况下Jackson这时会抛出异常, 含义是无法在Java对象中找到未知属性XXX
         * 但是, 我们有时候又需要允许JSON属性多于要产生的Java对象的属性. 比如, 你想从一个REST服务获取JSON数据, 但是它包含的内容远多于你所需要的.
         * 这是, 通过配置Jackson的Feature使能可以让你忽略那些多余的属性
         */
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);//默认为true,即json字符串中不能含有java对象中不存在的属性。
        //所有成员字段都可以序列化 ,而不仅仅是公共字段(默认设置)
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSerializer.setObjectMapper(objectMapper);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //redis解析key时，使用string序列化该对象，即转换为字符串
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        //redis解析value值时，使用json序列化该对象。

        //如果是pojo对象，则使用json双向序列化器
        if(!(tClass.newInstance() instanceof String)){
            redisTemplate.setValueSerializer(jacksonSerializer);
            redisTemplate.setHashValueSerializer(jacksonSerializer);
        }else {
            redisTemplate.setValueSerializer(stringRedisSerializer);
            redisTemplate.setHashValueSerializer(stringRedisSerializer);
        }
        return redisTemplate;
    }




    static <T> Map<String,T> test(Class<T> tClass){
        Map<String,T> map=new HashMap<>();
        T t= null;
        try {
            t = tClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if(t instanceof String)
            System.out.println("\n======adkfaodfhoiwqh=========\n");
        else
            System.out.println("==================================\n--------------------------------\n======================\n---------------\n");
        if(t!=null)
        System.out.println(t.getClass());
        else
            System.out.println(t);
        return map;
    }
    public static void main(String[] args) {
        test(Object.class);
//        String s="asdfg";
//        System.out.println(s instanceof Object);
//        System.out.println((Object)s instanceof String);
//        System.out.println((Object) null instanceof Object);
//        System.out.println(null instanceof Object);
    }

}
